/*
 * HashFile.h
 *
 */

#ifndef HASHFILE_H_
#define HASHFILE_H_

#include <list>
#include "../BlockFile.h"
#include "Bucket.h"
#include "HashTable.h"
#include "HashFunction.h"
#include "../blockManagment/BlockBitMap.h"
#include "../../log/Logger.h"
#include "../../utils/Utils.h"

#define BUCKET_EXT ".bkt"
#define TABLE_EXT ".tbl"
#define BITMAP_EXT ".bbm"

template<class T>
class HashFile : public IFile {
private:
	String path;
	BigInt bucketSize;
	float capacity;

	Bucket<T> bucket;
	HashTable table;
	BlockFile file;
	BlockBitMap bitmap;
	BigInt current;

public:
	HashFile(BigInt bucketSize, float capacity = 1);
	virtual ~HashFile();

	bool open(const String& path, bool create = false);
	void create(const String& path);
	void close();
	const String& getPath() const;

	void add(const T& record);
	bool find(BigInt hashcode, T& record);
	bool remove(BigInt hashcode);

protected:
	BigInt getIndex(BigInt hashcode) const;
	void handleOverflow(Bucket<T>& bucket);
	void handleTableOnOverflow(BigInt index, Bucket<T>& bucket, BigInt newBlock);
	void distribute(BigInt newBlock, Bucket<T>& newBucket);
	void saveBucket(BigInt block, BlockPersistor& persistor);
	void readBucket(BigInt bucket);
};

template<class T>
HashFile<T>::HashFile(BigInt bucketSize, float capacity): bucketSize(bucketSize), capacity(capacity), current(-1) {

}

template<class T>
HashFile<T>::~HashFile() {
	close();
}

template<class T>
void HashFile<T>::close() {
	table.close();
	bitmap.close();
	file.close();
}

template<class T>
bool HashFile<T>::open(const String& path, bool createNew) {
	if (table.open(path + TABLE_EXT, createNew) && file.open(path + BUCKET_EXT, createNew) && bitmap.open(path + BITMAP_EXT, createNew)) {
		table.load();
		table.close();
		bitmap.load();
		bitmap.close();
		return true;
	}
	if (createNew) {
		create(path);
		return true;
	}
	return false;
}

template<class T>
void HashFile<T>::create(const String& path) {
	table.create(path + TABLE_EXT);
	bitmap.create(path + BITMAP_EXT);
	file.create(path + BUCKET_EXT);
}

template<class T>
void HashFile<T>::add(const T& record) {
	BigInt index = getIndex(record.getHashcode());
	BigInt block = table.get(index);
	readBucket(block);
	bucket.add(record);
	BlockPersistor persistor(bucketSize, capacity);
	if (persistor.serialize(bucket)) {
		saveBucket(current, persistor);
	}
	else {
		bucket.remove(record.getHashcode());
		BigInt newBlock = bitmap.getFreeBlock();
		handleTableOnOverflow(index, bucket, newBlock);
		Bucket<T> newBucket(bucket.getDispersion());
		distribute(newBlock, newBucket);
		persistor.serialize(bucket);
		saveBucket(current, persistor);
		persistor.clear();
		persistor.serialize(newBucket);
		saveBucket(newBlock, persistor);
		add(record);
	}
}

template<class T>
void HashFile<T>::readBucket(BigInt index) {
	if (index != current) {
		BlockPersistor persistor(bucketSize, capacity);
		current = index;
		file.get(current, persistor);
		persistor.unserialize(bucket);
	}
}

template<class T>
bool HashFile<T>::find(BigInt hashcode, T& record) {
	BigInt block = table.get(getIndex(hashcode));
	readBucket(block);
	if (bucket.find(hashcode, record))
		return true;
	return false;
}

template<class T>
bool HashFile<T>::remove(BigInt hashcode) {
	BigInt index = getIndex(hashcode);
	BigInt block = table.get(index);
	readBucket(block);
	if (bucket.remove(hashcode)) {
		if (bucket.count() == 0) {
			BigInt previous = table.get(index - bucket.getDispersion()/2);
			BigInt next = table.get(index + bucket.getDispersion()/2);
			if (next == previous) {
				bitmap.free(block);
				readBucket(next);
				table.setCircular(index, bucket.getDispersion(), next);
				bucket.divideDispersion();
				while (table.reduce());
			}
		}
		else {
			BlockPersistor persistor(bucketSize, capacity);
			persistor.serialize(bucket);
			file.write(block, persistor);
		}
		return true;
	}
	return false;
}

template<class T>
void HashFile<T>::handleTableOnOverflow(BigInt index, Bucket<T>& bucket, BigInt newBlock) {
	if (bucket.getDispersion() == table.size()) {
		bucket.duplicateDispersion();
		table.duplicate();
		table.set(index, index, newBlock);
	}
	else {
		bucket.duplicateDispersion();
		table.setCircular(index, bucket.getDispersion(), newBlock);
	}
}

template<class T>
void HashFile<T>::distribute(BigInt newBlock, Bucket<T>& newBucket) {
	std::list<BigInt> recordsToErase;
	for (typename Bucket<T>::ConstHashIter it = bucket.begin(); it != bucket.end(); ++it) {
		if (table.get(getIndex(it->first)) == newBlock) {
			newBucket.add(*it->second);
			recordsToErase.push_back(it->first);
		}
	}
	for (std::list<BigInt>::const_iterator it = recordsToErase.begin(); it != recordsToErase.end(); ++it) {
		bucket.remove(*it);
	}
}

template<class T>
BigInt HashFile<T>::getIndex(BigInt hashcode) const {
	return hashcode % table.size();
}

template<class T>
const String& HashFile<T>::getPath() const {
	return path;
}

template<class T>
void HashFile<T>::saveBucket(BigInt block, BlockPersistor& persistor) {
	file.write(block, persistor);
	bitmap.take(block);
}

#endif /* HASHFILE_H_ */
